﻿/*****************************************************
 * File:            OnlineRandomWalkTesting.cs
 * Description:     Online Random Walk Testing
 * By:              Ali Khalili (khalili DOT ir AT gmail DOT com)
 * Project:         Testing Engine
 * Namespece		MBT.Conformance.OnlineTesting
 * License:
 *****************************************************/
using System;
using System.Collections.Generic;
using System.Collections;

using SMLib.Automata.IA;
using SMLib.Automata.Base;


namespace MBT.Conformance.OnlineTesting
{
    /// <summary>
    /// Online test case generation using Random Walk strategy (for Interface Automata)
    /// </summary>
    public class Onlt_RandomWalk_ioco
    {
        #region -=[ Configuration ]=-

        /// <summary>
        /// Maximum number of test cases to be generated (and executed)
        /// </summary>
        public static int MAX_NUM_TESTS = 50000;

        /// <summary>
        /// Maximum lenght of each test case (in which afterward, we reset the system)
        /// </summary>
        public static int MAX_TEST_LENGTH = 50;

        /// <summary>
        /// Try to find as much counterexample as possible (but not more than this number)
        /// </summary>
        public static int MAX_NUMBER_OF_CES = 20;

        #endregion

        InterfaceAutomaton model;
        IBlackBox_IA sut;
        Random rnd;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="model">The model</param>
        /// <param name="sulStepper">The SUT (system under testing)</param>
        public Onlt_RandomWalk_ioco(InterfaceAutomaton model, IBlackBox_IA sut)
        {
            this.model = model;
            this.sut = sut;
            rnd = new Random();
			if(!MBT.Base.LibSetting.IsInitialized)
			{
				Console.WriteLine("Warning: MBT lib setting was not initialized");
				MBT.Base.LibSetting.Init();				
			}
        }

        /// <summary>
        /// Verification that the founded test case is realy a counterexample (used in debug mode): examine test case on the model
        /// <remarks>This is just for debugging and should not be used in the release version</remarks>
        /// </summary>
        /// <param name="tc">counterexample</param>
        /// <returns>true if the test case passed. </returns>
        bool VerifyTestCase(List<InterfaceAutomaton.IA_Step> tc)
        {
            InterfaceAutomaton.IA_State currentNode = (InterfaceAutomaton.IA_State)model.InitialState;
            for (int i = 0; i < tc.Count; i++)
            {
                bool found = false;
                foreach (InterfaceAutomaton.IA_Step tr in currentNode.Transitions)
                {
                    if (tr.Equals(tc[i]))
                    {
                        found = true;
                        //* go to next state
                        currentNode = (InterfaceAutomaton.IA_State)tr.Destination;
                        break;
                    }
                }
                if (!found)
                    return false;
            }
            return true;
        }


        /// <summary>
        /// Check conformance of SUL and mode
        /// </summary>
        /// <returns>a counterexample (if found) or null (if all test cases passed)</returns>
        public List<InterfaceAutomaton.IA_Step> CheckConformance()
        {
            //* list of counterexamples
            List<List<InterfaceAutomaton.IA_Step>> ces = new List<List<InterfaceAutomaton.IA_Step>>();
            //* current test case
            List<InterfaceAutomaton.IA_Step> testCase;

            for (int numberOfTests = 0; numberOfTests < MAX_NUM_TESTS; numberOfTests++)
            {
                if (ces.Count > MAX_NUMBER_OF_CES)
                    break;

                #region -= [ One Test Case]=-

                //* reset the system
                sut.Reset();
                //* start a new test case
                testCase = new List<InterfaceAutomaton.IA_Step>();
                //* and initialize the walk from initial node
                InterfaceAutomaton.IA_State currentNode = (InterfaceAutomaton.IA_State)model.InitialState;

                for (int stepNumber = 0; stepNumber < MAX_TEST_LENGTH; stepNumber++)
                {
                    //* sink (dead) state!
                    if (currentNode.Transitions.Count == 0)
                        break;

                    bool considerItAsActive = false;

                    //* it has both input and output
                    if (currentNode.Status == InterfaceAutomaton.IA_State.StateType.BOTH)
                    {
                        //* usually, it is not likely (neither is prefferable)
                        double d = rnd.NextDouble();
                        if (d < 0.5)
                            considerItAsActive = true;
                        else
                            considerItAsActive = false;
                    }
                    else if (currentNode.Status == InterfaceAutomaton.IA_State.StateType.ACTIVE)
                    {
                        considerItAsActive = true;
                    }
                    else
                        considerItAsActive = false;

                    //* If state is active: wait for an event
                    if (considerItAsActive)
                    {
                        //****************************************************************************************
                        if (sut.EventAvailable())
                        {
                            bool found = false;
                            ISymbol seenEvent = sut.Event();
                            foreach (InterfaceAutomaton.IA_Step tr in currentNode.Transitions)
                            {
                                if (tr.Label.Equals(seenEvent))
                                {
                                    //* Stor the seen event
                                    testCase.Add(tr);

                                    //* go to next state
                                    currentNode = (InterfaceAutomaton.IA_State)tr.Destination;
                                    found = true;

                                    break;
                                }
                            }
                            //* Found a counterexample! have seen a prohibited output
                            if (!found)
                            {
                                //* Store the seen event: Nothing have been seen!!
                                InterfaceAutomaton.IA_Step seenStep = new InterfaceAutomaton.IA_Step(seenEvent, false);
                                testCase.Add(seenStep);
                                ces.Add(testCase);
                                break;
                            }
                        }
                        else
                        {
                            //* expecting for an output, but nothing have been seen!
                            testCase.Add(null);
                            ces.Add(testCase);
                            break;
                        }
                    }
                    //* else if state is passive: do an action
                    //* Note that if it is Mealy machine (wrapper in IA), it would always be passive!
                    else
                    {
                        //* select a random input (or function call)
                        int r = SelectRandomAction(currentNode);
                        //****************************************************************************************
                        if ((currentNode.Transitions[r]).StepType == InterfaceAutomaton.IA_Step.Step_Type.ACTION)
                        {
                            ISymbol inp = (ISymbol)(currentNode.Transitions[r]).Label;
                            //* Store the current step
                            testCase.Add(currentNode.Transitions[r]);
                            try
                            {
                                sut.Perform(inp);
                            }
                            catch (NotEnabledActionException)
                            {
                                Console.WriteLine("Action" + inp.ToString() + " is not enabled!");
                                ces.Add(testCase);
                                break;
                            }

                            //* go to next state
                            currentNode = (InterfaceAutomaton.IA_State)(currentNode.Transitions[r]).Destination;
                            continue;
                        }
                        //****************************************************************************************
                        else if ((currentNode.Transitions[r]).StepType == InterfaceAutomaton.IA_Step.Step_Type.FUNCTION_CALL)
                        {
                            ISymbol inp = ((PairOfSymbols)(currentNode.Transitions[r]).Label).Input;
                            try
                            {
                                sut.Perform(inp);
                            }
                            catch (NotEnabledActionException)
                            {
                                Console.WriteLine("The input \"{0}\" in the model is not enabled on the system!", inp);
                                testCase.Add(currentNode.Transitions[r]);
                                ces.Add(testCase);
                                break;
                            }

                            if (sut.EventAvailable())
                            {
                                ISymbol outputInSUT = sut.Event();

                                //* the function MIGHT be non-deterministic.
                                bool found = false;
                                foreach (InterfaceAutomaton.IA_Step trInModel in currentNode.Transitions)
                                {
                                    if (trInModel.StepType == InterfaceAutomaton.IA_Step.Step_Type.FUNCTION_CALL)
                                    {
                                        PairOfSymbols p = (PairOfSymbols)trInModel.Label;
                                        if (p.Input.Equals(inp) && p.Output.Equals(outputInSUT))
                                        {
                                            //* found the proper transition having the same result which happened in SUT
                                            found = true;
                                            testCase.Add(trInModel);
                                            currentNode = trInModel.Destination;
                                            break;
                                        }
                                    }
                                }

                                if (found)
                                    continue;
                                else
                                {
                                    //* there is no proper transition and some unexpected transition have been seen!
                                    InterfaceAutomaton.IA_Step seenStep = new InterfaceAutomaton.IA_Step(new PairOfSymbols(inp, outputInSUT));
                                    seenStep.SetDestination(null);
                                    testCase.Add(seenStep);
                                    ces.Add(testCase);
                                    break;
                                }
                            }
                            else
                            {
                                //* haven't seen this transition!
                                testCase.Add(currentNode.Transitions[r]);
                                ces.Add(testCase);
                                break;
                            }
                        }
                    }
                }
                #endregion

            }

            //* no counterexample found! return null
            if (ces.Count == 0)
                return null;

            //* a number of counterexamples found. return the shortest one!
            int minIndex = 0, minValue = ces[0].Count;
            for (int i = 1; i < ces.Count; i++)
            {
                if (ces[i].Count < minValue)
                {
                    minIndex = i; minValue = ces[i].Count;
                }
            }

            //return Shorten(ces[minIndex]);
            return (ces[minIndex]);
        }

        /// <summary>
        /// Select a random -controllable- action
        /// </summary>
        /// <param name="currentNode">current node</param>
        /// <returns>index of action</returns>
        private int SelectRandomAction(InterfaceAutomaton.IA_State currentNode)
        {
            int numberOfInputs = 0;
            for (int hh = 0; hh < currentNode.Transitions.Count; hh++)
            {
                if ((currentNode.Transitions[hh]).StepType == InterfaceAutomaton.IA_Step.Step_Type.ACTION ||
                    (currentNode.Transitions[hh]).StepType == InterfaceAutomaton.IA_Step.Step_Type.FUNCTION_CALL)
                    numberOfInputs++;
            }
            int r = rnd.Next(numberOfInputs);
            numberOfInputs = 0;
            for (int hh = 0; hh < currentNode.Transitions.Count; hh++)
            {
                if ((currentNode.Transitions[hh]).StepType == InterfaceAutomaton.IA_Step.Step_Type.ACTION ||
                    (currentNode.Transitions[hh]).StepType == InterfaceAutomaton.IA_Step.Step_Type.FUNCTION_CALL)
                {
                    if (numberOfInputs < r)
                        numberOfInputs++;
                    else
                    {
                        r = hh;
                        break;
                    }
                }
            }
            return r;
        }


        //const int MAXIMUM_NUMBER_OF_TRIAL_FOR_SHORTENING = 300;
        ///// <summary>
        ///// Trying to shorten counterexample
        ///// </summary>
        ///// <param name="ce"></param>
        ///// <returns></returns>
        //private List<InterfaceAutomaton.IA_Step> Shorten(List<InterfaceAutomaton.IA_Step> ce)
        //{
        //Graph.GraphShortestPath<InterfaceAutomaton.IA_State, InterfaceAutomaton.IA_Step> sp; 
        //    sp = new Graph.GraphShortestPath<InterfaceAutomaton.IA_State, InterfaceAutomaton.IA_Step>(model, null, null);
        //    sp.ComputeShortestPathes();
        //    if (ce == null || ce.Count < 2)
        //        return ce;
        //    List<int> trace = new List<int>();
        //    InterfaceAutomaton.IA_State currentNode = (InterfaceAutomaton.IA_State)model.InitialState;
        //    trace.Add(0);
        //    for (int i = 0; i < ce.Count - 1; i++)
        //    {
        //        foreach (InterfaceAutomaton.IA_Step tr in currentNode.Transitions)
        //        {
        //            if (tr.Equals(ce[i]))
        //            {
        //                //* go to next state
        //                currentNode = (InterfaceAutomaton.IA_State)tr.Destination;
        //                trace.Add(model.IndexOf(currentNode));
        //                break;
        //            }
        //        }
        //    }
        //    for (int y = ce.Count - 2; y > 0; y--)
        //    {
        //        //* shorten the first part of counterexample
        //        IList tmp = sp.ShortestPath(trace[y]);
        //        List<InterfaceAutomaton.IA_Step> tmpCE = new List<InterfaceAutomaton.IA_Step>();
        //        for (int u = 0; u < tmp.Count; u++)
        //            tmpCE.Add(tmp[u]);
        //        //* stick the second part as the same
        //        for (int u = y; u < ce.Count; u++)
        //            tmpCE.Add(ce[u]);
        //        //* now try it to see if it is still a counterexample or not
        //        for (int i = 0; i < MAXIMUM_NUMBER_OF_TRIAL_FOR_SHORTENING; i++)
        //        {
        //            if (!VerifyTestCase(tmpCE))
        //                return tmpCE;
        //        }
        //    }
        //    return ce;
        //}
    }
}
